package com.dz.finance.biz.service.impl;

import cn.hutool.captcha.CaptchaUtil;
import cn.hutool.captcha.LineCaptcha;
import com.dz.finance.biz.constant.MsgConstant;
import com.dz.finance.biz.constant.RedisKeyConstant;
import com.dz.finance.biz.dto.PhoneSmsLoginDTO;
import com.dz.finance.common.constant.SystemConstant;
import com.dz.finance.biz.dto.PhonePasswordLoginDTO;
import com.dz.finance.biz.dto.SmsCodeDTO;
import com.dz.finance.biz.entity.Member;
import com.dz.finance.biz.entity.MemberBindPhone;
import com.dz.finance.biz.entity.SmsCode;
import com.dz.finance.common.entity.User;
import com.dz.finance.biz.enums.SmsCodeTypeEnum;
import com.dz.finance.biz.service.MemberBindPhoneService;
import com.dz.finance.biz.service.MemberLoginService;
import com.dz.finance.biz.service.MemberService;
import com.dz.finance.common.service.UserTokenService;
import com.dz.finance.common.constant.ApiResponseCode;
import com.dz.finance.common.entity.Token;
import com.dz.finance.common.exception.BizException;
import com.dz.finance.common.exception.ParameterException;
import com.dz.finance.common.properties.GuoyangyunProperties;
import com.dz.finance.common.utils.BaseUtils;
import com.dz.finance.common.utils.DateUtils;
import com.dz.finance.common.service.HttpClient;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.util.Strings;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import java.time.Duration;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * @author: DengHaoQi
 * @date: 2024/5/22 16:43
 * @description:
 */

@Service
@Slf4j
@RequiredArgsConstructor
public class MemberLoginServiceImpl implements MemberLoginService {

    private final RedisTemplate<String, Object> redisTemplate;
    private final MemberBindPhoneService memberBindPhoneService;
    private final HttpClient httpClient;
    private final GuoyangyunProperties property;
    private final MemberService memberService;
    private final UserTokenService<User> userTokenService;
    private final PasswordEncoder passwordEncoder;
    private final ObjectMapper objectMapper;


    /*
     * 获取随机生成的UUID,用于临时身份标识
     * */
    @Override
    public String getClientId() {
        return UUID.randomUUID().toString().replace("-", "");
    }


    /*
     * 获取图形验证码
     * */
    @Override
    public String getBase64Code(String clientId) {
        // 创建图形验证码
        LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(300, 192, 5, 1000);
        String code = lineCaptcha.getCode();
        redisTemplate.opsForValue().set(RedisKeyConstant.GRAPHIC_VERIFICATION_CODE + clientId, code, 5, TimeUnit.MINUTES);
        log.info("客户端id: {}, 图形验证码: {}", clientId, code);
        return lineCaptcha.getImageBase64();
    }


    /*
     * 获取短信验证码
     * */
    @Override
    public void sendSmsCode(SmsCodeDTO smsCodeDTO) {
        // 校验图形验证码
        checkBase64Code(smsCodeDTO.getClientId(), smsCodeDTO.getCode());
        String key = RedisKeyConstant.SMS_CODE + smsCodeDTO.getSmsCodeType() + smsCodeDTO.getPhone();
        SmsCode smsCode = (SmsCode) redisTemplate.opsForValue().get(key);
        if (Objects.nonNull(smsCode)) {
            // 获取当前请求与验证码存入缓存时间的时间差
            Duration duration = DateUtils.getDuration(smsCode.getCacheDate(), DateUtils.getSystemTime());
            if (duration.getSeconds() < SystemConstant.CODE_SENDING_CYCLE) {
                throw new BizException(MsgConstant.GET_CODE_TOO_FREQUENT);
            }
        }
        // 判断当前操作是登陆还是注册
        MemberBindPhone mp = new MemberBindPhone();
        mp.setPhone(smsCodeDTO.getPhone()); // 以手机号作为查询条件
        MemberBindPhone memberBindPhone = memberBindPhoneService.getMemberBindPhone(mp);

        if (SmsCodeTypeEnum.REG.getCode().equals(smsCodeDTO.getSmsCodeType()) && Objects.nonNull(memberBindPhone)) {
            throw new BizException(MsgConstant.PHONE_REGISTERED);
        }

        if (SmsCodeTypeEnum.LOGIN.getCode().equals(smsCodeDTO.getSmsCodeType()) && Objects.isNull(memberBindPhone)) {
            throw new BizException(MsgConstant.PHONE_NOT_REGISTERED);
        }

        smsCode = SmsCode.builder()
                .code(String.valueOf(BaseUtils.getRandom(6)))
                .cacheDate(DateUtils.getSystemTime())
                .build();
        // 短信验证码5分钟有效
        redisTemplate.opsForValue().set(key, smsCode, 5, TimeUnit.MINUTES);
        log.info("客户端id: {}, 手机号: {}, 短信验证码: {}", smsCodeDTO.getClientId(), smsCodeDTO.getPhone(), smsCode.getCode());

        // 构造第三方短信接口的请求参数
        Map<String, String> paramMap = new HashMap<>();
        paramMap.put("appkey", property.getAppKey());
        paramMap.put("appsecret", property.getAppSecret());
        paramMap.put("smsSignId", property.getSmsSignId()); // 签名ID
        paramMap.put("templateId", property.getTemplateId()); // 模板ID
        paramMap.put("mobile", smsCodeDTO.getPhone()); // 手机号码
        paramMap.put("param", "**code**:" + smsCode.getCode()); // 变量 格式 k:v,k:v 例如[**code**:8372]
        // 调用第三方短信接口
        httpClient.smsSend(paramMap);
    }


    /*
     * 校验短信验证码
     * */
    @Override
    public boolean checkSmsCode(String phone, String code, String smsCodeType) {
        SmsCode smsCode = (SmsCode) redisTemplate.opsForValue().get(RedisKeyConstant.SMS_CODE + smsCodeType + phone);
        if (Objects.isNull(smsCode)) {
            return false;
        } else if (smsCode.getCode().equals(code)) {
            // 短信验证码校验成功立即删除缓存
            redisTemplate.delete(RedisKeyConstant.SMS_CODE + smsCodeType + phone);
            return true;
        }
        return false;
    }


    /*
     * 手机号密码登陆
     * */
    @Override
    public Token loginByPhonePassword(PhonePasswordLoginDTO phonePasswordLoginDTO) {
        // 校验图形验证码
        checkBase64Code(phonePasswordLoginDTO.getClientId(), phonePasswordLoginDTO.getCode());

        // 根据手机号查询用户
        MemberBindPhone mbp = new MemberBindPhone();
        mbp.setPhone(phonePasswordLoginDTO.getPhone());
        MemberBindPhone memberBindPhone = memberBindPhoneService.getMemberBindPhone(mbp);
        if (Objects.isNull(memberBindPhone) || Strings.isBlank(phonePasswordLoginDTO.getPassword())) {
            throw new BizException(ApiResponseCode.ACCOUNT_PASSWORD_ERROR.getCode(), ApiResponseCode.ACCOUNT_PASSWORD_ERROR.getMessage());
        }

        // 密码校验
        if (!passwordEncoder.matches(phonePasswordLoginDTO.getPassword(), memberBindPhone.getPassword())) {
            throw new BizException(ApiResponseCode.ACCOUNT_PASSWORD_ERROR.getCode(), ApiResponseCode.ACCOUNT_PASSWORD_ERROR.getMessage());
        }

        Member condition = new Member();
        condition.setId(memberBindPhone.getMemberId());
        Member member = memberService.getMember(condition);
        return loginSuccess(member.getId(), member.getTenantId(), member.getSysRoleIds());
    }


    /*
    * 手机号短信验证码登陆
    * */
    @Override
    public Token loginByPhoneSms(PhoneSmsLoginDTO phoneSmsLoginDTO) {
        // 校验短信验证码
        boolean checkSmsCode = this.checkSmsCode(phoneSmsLoginDTO.getPhone(), phoneSmsLoginDTO.getSmsCode(), SmsCodeTypeEnum.LOGIN.getCode());
        if (!checkSmsCode) {
            throw new ParameterException(MsgConstant.PASSCODE_ERROR);
        }
        // 根据手机号查询用户
        MemberBindPhone mbp = new MemberBindPhone();
        mbp.setPhone(phoneSmsLoginDTO.getPhone());
        MemberBindPhone memberBindPhone = memberBindPhoneService.getMemberBindPhone(mbp);
        if (Objects.isNull(memberBindPhone)) {
            throw new ParameterException(MsgConstant.PHONE_NOT_REGISTERED);
        }
        Member condition = new Member();
        condition.setId(memberBindPhone.getMemberId());
        Member member = memberService.getMember(condition);
        return loginSuccess(member.getId(),member.getTenantId(), member.getSysRoleIds());
    }


    /*
    * 获取客户端登陆Token
    * */
    @Override
    public Token getClientToken(String clientId) {
        return (Token) redisTemplate.opsForValue().get(RedisKeyConstant.CLIENT_TOKEN_KEY + clientId);
    }


    /*
     * 登陆逻辑
     * */
    private Token loginSuccess(Long memberId, Long tenantId, String sysRoleIds) {
        try {
            User user = new User();
            user.setId(memberId);
            user.setTenantId(tenantId);
            user.setSysRoleIds(objectMapper.readValue(sysRoleIds, new TypeReference<Set<Long>>() {}));
            userTokenService.buildToken(user);
            redisTemplate.opsForValue().set(RedisKeyConstant.TOKEN_CACHE_KEY + user.getToken().getToken(), user, user.getToken().getExpire(), TimeUnit.SECONDS);
            log.info("登陆用户id: {}; token: {}",user.getId(),user.getToken().getToken());
            return user.getToken();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }


    /*
     * 利用clientId获取Redis中图形验证码并校验是否正确
     * */
    private void checkBase64Code(String clientId, String code) {
        String base64Code = (String) redisTemplate.opsForValue().get(RedisKeyConstant.GRAPHIC_VERIFICATION_CODE + clientId);
        try {
            Assert.hasText(base64Code, MsgConstant.BASE64CODE_ERROR);
            // 断言处没抛异常则说明验证码正确且立即被使用，所以使用完删除验证码缓存
            if (!code.equalsIgnoreCase(base64Code)) {
                throw new IllegalArgumentException(MsgConstant.BASE64CODE_ERROR);
            }
            redisTemplate.delete(RedisKeyConstant.GRAPHIC_VERIFICATION_CODE + clientId);
        } catch (IllegalArgumentException e) {
            throw new ParameterException(e.getMessage());
        }
    }


}
